import React, { Component } from 'react';
import { I18nextProvider } from 'react-i18next';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { HashRouter as Router } from 'react-router-dom';
import { hot } from 'react-hot-loader/root';

import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';

import ErrorBoundaryDialog from './components/ErrorBoundaryDialog';

import {
  CommandsManager,
  ExtensionManager,
  ServicesManager,
  HotkeysManager,
  MeasurementService,
  utils,
  redux,
} from '@ohif/core';

import i18n from '@ohif/i18n';

// TODO: This should not be here
//import './config';
import { setConfiguration } from './config';

/** Utils */
import {
  initWebWorkers,
} from './utils/index.js';

/** Extensions */
import { GenericViewerExtension, SystemSettingsExtension } from './appExtensions';

/** Viewer */
import StandaloneViewer from './StandaloneViewer';

/** Store */
import { getActiveContexts } from './store/layout/selectors.js';
import store from './store';

/** Contexts */
import { AppProvider, useAppContext, CONTEXTS } from './context/AppContext';
import { SynchronizerProvider, useSynchronizerContext } from './context/SynchronizerContext';

import { retrieveMeasurements, storeMeasurements } from './apis';

/** ~~~~~~~~~~~~~ Application Setup */
const commandsManagerConfig = {
  getAppState: () => store.getState(),
  getActiveContexts: () => getActiveContexts(store.getState()),
};

/** Managers */
const commandsManager = new CommandsManager(commandsManagerConfig);
const servicesManager = new ServicesManager();
const hotkeysManager = new HotkeysManager(commandsManager, servicesManager);
let extensionManager;
/** ~~~~~~~~~~~~~ End Application Setup */

// TODO[react] Use a provider when the whole tree is React
window.store = store;

window.ohif = window.ohif || {};
window.ohif.app = {
  commandsManager,
  hotkeysManager,
  servicesManager,
  extensionManager,
};

class App extends Component {
  static propTypes = {
    config: PropTypes.oneOfType([
      PropTypes.func,
      PropTypes.shape({
        routerBasename: PropTypes.string.isRequired,
        oidc: PropTypes.array,
        whiteLabeling: PropTypes.shape({
          createLogoComponentFn: PropTypes.func,
        }),
        extensions: PropTypes.array,
      }),
    ]).isRequired,
    defaultExtensions: PropTypes.array,
  };

  static defaultProps = {
    config: {
      oidc: [],
      extensions: [],
    },
    defaultExtensions: [],
  };

  _appConfig;

  constructor(props) {
    super(props);

    const { config, defaultExtensions } = props;

    const appDefaultConfig = {
      xhrTokenKey: 'Authorization-test',
      cornerstoneExtensionConfig: {},
      extensions: [],
      routerBasename: '/',
    };

    this._appConfig = {
      ...appDefaultConfig,
      ...(typeof config === 'function' ? config({ servicesManager }) : config),
    };

    const {
      servers,
      cornerstoneExtensionConfig,
      extensions,
    } = this._appConfig;

    setConfiguration(this._appConfig);

    _initServices([MeasurementService]);

    _initMeasurementService();

    _initExtensions(
      [...defaultExtensions, ...extensions],
      cornerstoneExtensionConfig,
      this._appConfig
    );

    /*
     * Must run after extension commands are registered
     * if there is no hotkeys from localStorage set up from config.
     */
    _initHotkeys();
    _initServers(servers);
    initWebWorkers();
  }

  render() {
    const { routerBasename } = this._appConfig;

    return (
      <ErrorBoundaryDialog context="App">
        <Provider store={store}>
          <AppProvider config={this._appConfig}>
            <I18nextProvider i18n={i18n}>
              <SynchronizerProvider>
                <Router basename={routerBasename}>
                  <StandaloneViewer />
                </Router>
              </SynchronizerProvider>
            </I18nextProvider>
          </AppProvider>
        </Provider>
      </ErrorBoundaryDialog>
    );
  }
}

function _initServices(services) {
  servicesManager.registerServices(services);
}

function _initMeasurementService() {
  const { MeasurementService } = servicesManager.services;
  if (MeasurementService) {
    const options = {
      retrieve: retrieveMeasurements,
      store: storeMeasurements,
    };
    MeasurementService.createMeasurementApi(options);
  }
}

/**
 * @param
 */
function _initExtensions(extensions, cornerstoneExtensionConfig, appConfig) {
  extensionManager = new ExtensionManager({
    commandsManager,
    servicesManager,
    appConfig,
    api: {
      contexts: CONTEXTS,
      hooks: {
        useAppContext,
        useSynchronizerContext,
      },
    },
  });

  const requiredExtensions = [
    GenericViewerExtension,
    SystemSettingsExtension,
    [OHIFCornerstoneExtension, cornerstoneExtensionConfig],
  ];

  const mergedExtensions = requiredExtensions.concat(extensions);
  extensionManager.registerExtensions(mergedExtensions);
}

/**
 *
 * @param {Object} appConfigHotkeys - Default hotkeys, as defined by app config
 */
function _initHotkeys() {
  const { preferences: { hotkeys } } = store.getState();
  hotkeysManager.setHotkeys(hotkeys);
  const { defaultPreferences: { defaultHotkeys } } = redux;
  hotkeysManager.setDefaultHotKeys(defaultHotkeys);
}

function _initServers(servers) {
  if (servers) {
    utils.addServers(servers, store);
  }
}

function _isAbsoluteUrl(url) {
  return url.includes('http://') || url.includes('https://');
}

function _makeAbsoluteIfNecessary(url, base_url) {
  if (_isAbsoluteUrl(url)) {
    return url;
  }

  /*
   * Make sure base_url and url are not duplicating slashes.
   */
  if (base_url[base_url.length - 1] === '/') {
    base_url = base_url.slice(0, base_url.length - 1);
  }

  return base_url + url;
}

/*
 * Only wrap/use hot if in dev.
 */
const ExportedApp = process.env.NODE_ENV === 'development' ? hot(App) : App;

export default ExportedApp;
export { commandsManager, extensionManager, hotkeysManager, servicesManager };
